#include "ogl/model/Shader.h"

Ogl::Model::Shader::Shader()
{	


	const char* vCode = R"(

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;


layout(std140) uniform PassBuffer
{

    mat4 proj,view;
    vec3 viewPos;
    float passPad;

};


layout(std140) uniform ObjBuffer
{
    mat4 world;
    mat4 worldInvTranspose;


};





void main()
{
    FragPos = vec3(world * vec4(aPos, 1.0));
    Normal = mat3(worldInvTranspose) * aNormal;  
    TexCoords = aTexCoords;
    
    gl_Position = proj * view * vec4(FragPos, 1.0);
}
)";

    const char *fCode = R"(

#version 330 core
out vec4 FragColor;

struct Material {
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
}; 

struct DirLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 direction;
    float pad;

};

struct PointLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 position;
    float pad3;

    float constant;
    float linear;
    float quadratic;
    float pad4;
};

struct SpotLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 position;
    float cutOff;
    vec3 direction;
    float outerCutOff;
  
    float constant;
    float linear;
    float quadratic;
    float pad;

   
};




#define MAX_DIRECTION_NUM 8
#define MAX_POINT_NUM 8
#define MAX_SPOT_NUM 8


struct LightEffect {

    DirLight dirLights[MAX_DIRECTION_NUM];
    PointLight pointLights[MAX_POINT_NUM];
    SpotLight spotLights[MAX_SPOT_NUM];

    int numDirs;
    int numPoints ;
    int numSpots;
    int pad;
};



in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;


layout(std140) uniform PassBuffer
{

    mat4 proj,view;
    vec3 viewPos;
    float passPad;

};

layout(std140) uniform LightBuffer
{
    LightEffect lightEffect;
};

layout(std140) uniform MatBuffer
{
    Material material;
};

// function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);

void main()
{    
    // properties
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);
    

    
    vec3 result=vec3(0.f,0.f,0.f);


    // phase 1 dir light
    for(int i = 0; i < lightEffect.numDirs; i++)
        result += CalcDirLight(lightEffect.dirLights[i], norm, viewDir);
    // phase 2: point lights
    for(int i = 0; i < lightEffect.numPoints; i++)
        result += CalcPointLight(lightEffect.pointLights[i], norm, FragPos, viewDir);    
    // phase 3: spot light
    for(int i = 0; i < lightEffect.numSpots; i++)
        result += CalcSpotLight(lightEffect.spotLights[i], norm, FragPos, viewDir);    
    
    FragColor = vec4(result, 1.0);
}

// calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
    vec3 lightDir = normalize(-light.direction);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // combine results
    //vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    //vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    //vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz.xyz;
    vec3 specular = light.specular * spec * material.specular.w;
    return (ambient + diffuse + specular);
}

// calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // combine results
    //vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    //vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    //vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular * spec * material.specular.w;

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
}

// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // spotlight intensity
    float theta = dot(lightDir, normalize(-light.direction)); 
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
    // combine results
    // vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    // vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    // vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular * spec * material.specular.w;

    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}

)";

    CreateFromCode(vCode, fCode);

}